﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using EnvDTE;
using EnvDTE80;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.TeamFoundation;
using Microsoft.Win32;
using Process = System.Diagnostics.Process;

namespace BuildComparisionExtension
{
   /// <summary>
   /// This is the class that implements the package exposed by this assembly.
   ///
   /// The minimum requirement for a class to be considered a valid package for Visual Studio
   /// is to implement the IVsPackage interface and register itself with the shell.
   /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
   /// to do it: it derives from the Package class that provides the implementation of the 
   /// IVsPackage interface and uses the registration attributes defined in the framework to 
   /// register itself and its components with the shell.
   /// </summary>
   // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
   // a package.
   [PackageRegistration( UseManagedResourcesOnly = true )]
   // This attribute is used to register the informations needed to show the this package
   // in the Help/About dialog of Visual Studio.
   [InstalledProductRegistration( "#110", "#112", "1.0", IconResourceID = 400 )]
   // This attribute is needed to let the shell know that this package exposes some menus.
   [ProvideMenuResource( "Menus.ctmenu", 1 )]
   [Guid( GuidList.guidBuildComparisionExtensionPkgString )]
   [ProvideAutoLoad( "{E13EEDEF-B531-4afe-9725-28A69FA4F896}" )]
   public sealed class BuildComparisionExtensionPackage : Package
   {
      public string LeftBuildDefinitionForComparison
      {
         get;
         private set;
      }

      private bool _checkedPowerTools;
      private bool _arePowerToolsInstalled;
      public bool ArePowerToolsInstalled
      {
         get
         {
            if ( !_checkedPowerTools )
            {
               _arePowerToolsInstalled = IsTfptDecember2011Installed();
               _checkedPowerTools = true;
            }

            return _arePowerToolsInstalled;
         }
      }

      /// <summary>
      /// Default constructor of the package.
      /// Inside this method you can place any initialization code that does not require 
      /// any Visual Studio service because at this point the package object is created but 
      /// not sited yet inside Visual Studio environment. The place to do all the other 
      /// initialization is the Initialize method.
      /// </summary>
      public BuildComparisionExtensionPackage()
      {
         Trace.WriteLine( string.Format( CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString() ) );
      }

      /////////////////////////////////////////////////////////////////////////////
      // Overriden Package Implementation
      #region Package Members

      /// <summary>
      /// Initialization of the package; this method is called right after the package is sited, so this is the place
      /// where you can put all the initilaization code that rely on services provided by VisualStudio.
      /// </summary>
      protected override void Initialize()
      {
         Trace.WriteLine( string.Format( CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString() ) );
         base.Initialize();

         // Add our command handlers for menu (commands must exist in the .vsct file)
         var oleMenuCommandService = GetService( typeof( IMenuCommandService ) ) as OleMenuCommandService;
         if ( null != oleMenuCommandService )
         {
            var leftBuildDefinitionCommandId = new CommandID( GuidList.guidBuildComparisionExtensionCmdSet, (int) PkgCmdIdList.cmdidLeftBuildDefinition );
            var leftBuildDefinitionMenuCommand = new OleMenuCommand( SelectLeftBuildDefinitionForComparison, leftBuildDefinitionCommandId );
            leftBuildDefinitionMenuCommand.BeforeQueryStatus += LeftBuildDefinitionMenuCommandOnBeforeQueryStatus;

            LeftBuildDefinitionMenuCommandOnBeforeQueryStatus( leftBuildDefinitionMenuCommand, new EventArgs() );

            oleMenuCommandService.AddCommand( leftBuildDefinitionMenuCommand );


            var rightBuildDefinitionCommandId = new CommandID( GuidList.guidBuildComparisionExtensionCmdSet, (int) PkgCmdIdList.cmdidRightBuildDefinition );
            var rightBuildDefinitionMenuCommand = new OleMenuCommand( RunComparison, rightBuildDefinitionCommandId );
            rightBuildDefinitionMenuCommand.BeforeQueryStatus += RightBuildDefinitionMenuCommandOnBeforeQueryStatus;

            RightBuildDefinitionMenuCommandOnBeforeQueryStatus( leftBuildDefinitionMenuCommand, new EventArgs() );

            oleMenuCommandService.AddCommand( rightBuildDefinitionMenuCommand );


            var installPowerToolsCommandId = new CommandID( GuidList.guidBuildComparisionExtensionCmdSet, (int) PkgCmdIdList.cmdidInstallPowerTools );
            var installPowerToolsMenuCommand = new OleMenuCommand( LaunchPowerToolsWebsite, installPowerToolsCommandId );
            installPowerToolsMenuCommand.BeforeQueryStatus += InstallPowerToolsMenuCommandOnBeforeQueryStatus;

            InstallPowerToolsMenuCommandOnBeforeQueryStatus( leftBuildDefinitionMenuCommand, new EventArgs() );

            oleMenuCommandService.AddCommand( installPowerToolsMenuCommand );
         }
      }

      private void InstallPowerToolsMenuCommandOnBeforeQueryStatus( object sender, EventArgs eventArgs )
      {
         var oleMenuCommand = sender as OleMenuCommand;

         if ( oleMenuCommand != null )
         {
            oleMenuCommand.Visible = !ArePowerToolsInstalled;
            oleMenuCommand.Enabled = !ArePowerToolsInstalled;
         }
      }

      private void RightBuildDefinitionMenuCommandOnBeforeQueryStatus( object sender, EventArgs eventArgs )
      {
         var oleMenuCommand = sender as OleMenuCommand;

         if ( oleMenuCommand != null )
         {
            oleMenuCommand.Visible = ArePowerToolsInstalled;
            oleMenuCommand.Enabled = ArePowerToolsInstalled && !string.IsNullOrWhiteSpace( LeftBuildDefinitionForComparison );
         }
      }

      private void LeftBuildDefinitionMenuCommandOnBeforeQueryStatus( object sender, EventArgs eventArgs )
      {
         var oleMenuCommand = sender as OleMenuCommand;

         if ( oleMenuCommand != null )
         {
            oleMenuCommand.Visible = ArePowerToolsInstalled;
            oleMenuCommand.Enabled = ArePowerToolsInstalled;
         }
      }
      #endregion

      public static bool IsTfptDecember2011Installed()
      {
         string nameOfApplication = "TfptPackage";

         // search in: CurrentUser
         string keyName = @"SOFTWARE\Microsoft\VisualStudio\10.0_Config\InstalledProducts";
         if ( ExistsInSubKey( Registry.CurrentUser, keyName, nameOfApplication ) )
         {
            return true;
         }

         // search in: LocalMachine_32
         keyName = @"SOFTWARE\Microsoft\VisualStudio\10.0_Config\InstalledProducts";
         if ( ExistsInSubKey( Registry.LocalMachine, keyName, nameOfApplication ) )
         {
            return true;
         }

         // search in: LocalMachine_64
         keyName = @"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0_Config\InstalledProducts";
         if ( ExistsInSubKey( Registry.LocalMachine, keyName, nameOfApplication ) )
         {
            return true;
         }

         return false;
      }

      private static bool ExistsInSubKey( RegistryKey root, string subKeyName, string nameOfApplication )
      {
         using ( RegistryKey key = root.OpenSubKey( subKeyName ) )
         {
            if ( key != null )
            {
               foreach ( string kn in key.GetSubKeyNames() )
               {
                  RegistryKey subkey;
                  using ( subkey = key.OpenSubKey( kn ) )
                  {
                     if ( subkey != null )
                     {
                        if ( subkey.Name.EndsWith( nameOfApplication, true, CultureInfo.InvariantCulture ) )
                        {
                           var pid = subkey.GetValue( "PID" ) as string;
                           if ( pid != null && pid.Equals( "10.0.41206.0", StringComparison.OrdinalIgnoreCase ) )
                           {
                              return true;
                           }
                        }
                     }
                  }
               }
            }
         }

         return false;
      }

      private void LaunchPowerToolsWebsite( object sender, EventArgs e )
      {
         Process.Start( "http://visualstudiogallery.msdn.microsoft.com/c255a1e4-04ba-4f68-8f4e-cd473d6b971f?SRC=Home" );
      }

      private void RunComparison( object sender, EventArgs e )
      {
         var dte = GetGlobalService( typeof( DTE ) ) as DTE;

         if ( dte != null )
         {
            var ext = dte.GetObject( "Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt" ) as TeamFoundationServerExt;

            if ( ext != null )
            {
               using ( var tfsTeamProjectCollection = new TfsTeamProjectCollection( new Uri( ext.ActiveProjectContext.DomainUri ) ) )
               {
                  var versionControlServer = tfsTeamProjectCollection.GetService<VersionControlServer>();
                  var buildServer = (IBuildServer) tfsTeamProjectCollection.GetService( typeof( IBuildServer ) );

                  string rightBuildDefinitionForComparison = GetBuildDefinitionForComparison();

                  string rightBuildDefinitionProject;
                  string leftBuildDefinitionProject;
                  SetLeftAndRightBuildDefinitionProjects( rightBuildDefinitionForComparison, buildServer, versionControlServer, out rightBuildDefinitionProject, out leftBuildDefinitionProject );

                  var processStartInfo = BuildProcessStartInfo( versionControlServer, tfsTeamProjectCollection, leftBuildDefinitionProject,
                                                                rightBuildDefinitionProject, rightBuildDefinitionForComparison );

                  StartComparisonProcess( processStartInfo );
               }
            }
         }
      }

      private void SetLeftAndRightBuildDefinitionProjects( string rightBuildDefinitionForComparison, IBuildServer buildServer,
                                                             VersionControlServer versionControlServer, out string rightBuildDefinitionProject,
                                                             out string leftBuildDefinitionProject )
      {
         leftBuildDefinitionProject = string.Empty;
         rightBuildDefinitionProject = string.Empty;
         foreach ( var teamProject in versionControlServer.GetAllTeamProjects( true ) )
         {
            var buildDefinitions = buildServer.QueryBuildDefinitions( teamProject.Name );

            if ( buildDefinitions.Any( bd => string.Compare( bd.Name, LeftBuildDefinitionForComparison, true, CultureInfo.InvariantCulture ) == 0 ) )
            {
               leftBuildDefinitionProject = teamProject.Name;
            }

            if ( buildDefinitions.Any( bd => string.Compare( bd.Name, rightBuildDefinitionForComparison, true, CultureInfo.InvariantCulture ) == 0 ) )
            {
               rightBuildDefinitionProject = teamProject.Name;
            }

            if ( !string.IsNullOrWhiteSpace( leftBuildDefinitionProject ) && !string.IsNullOrWhiteSpace( rightBuildDefinitionProject ) )
            {
               break;
            }
         }
      }

      private void StartComparisonProcess( ProcessStartInfo processStartInfo )
      {
         using ( var tfptProcess = Process.Start( processStartInfo ) )
         {
            string error = tfptProcess.StandardError.ReadToEnd();

            tfptProcess.WaitForExit();

            if ( !string.IsNullOrWhiteSpace( error ) )
            {
               Guid clsid = Guid.Empty;

               var uiShell = GetService( typeof( SVsUIShell ) ) as IVsUIShell;
               if ( uiShell != null )
               {
                  int result;

                  ErrorHandler.ThrowOnFailure( uiShell.ShowMessageBox( 0, ref clsid, "Error", error, string.Empty, 0, OLEMSGBUTTON.OLEMSGBUTTON_OK,
                                                                       OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_CRITICAL, 0, out result ) );
               }
            }
         }
      }

      private ProcessStartInfo BuildProcessStartInfo( VersionControlServer versionControlServer, TfsTeamProjectCollection tfsTeamProjectCollection,
                                                      string leftBuildDefinitionProject, string rightBuildDefinitionProject, string rightBuildDefinitionForComparison )
      {
         var processArguments = BuildProcessArguments( rightBuildDefinitionForComparison, leftBuildDefinitionProject, rightBuildDefinitionProject );

         var workingDirectory = GetWorkingDirectoryOfCurrentWorkspace( tfsTeamProjectCollection, versionControlServer );

         var processStartInfo = new ProcessStartInfo( "tfpt", processArguments )
                                {
                                   WorkingDirectory = workingDirectory,
                                   UseShellExecute = false,
                                   RedirectStandardError = true,
                                   RedirectStandardOutput = true
                                };
         return processStartInfo;
      }

      private string BuildProcessArguments( string rightBuildDefinitionForComparison, string leftBuildDefinitionProject, string rightBuildDefinitionProject )
      {
         string leftBuildDefinition = string.Format( @"{0}\{1}", leftBuildDefinitionProject, LeftBuildDefinitionForComparison );
         string rightBuildDefinition = string.Format( @"{0}\{1}", rightBuildDefinitionProject, rightBuildDefinitionForComparison );

         string processArguments = string.Format( @"builddefinition /Diff ""{0}"" ""{1}""", leftBuildDefinition, rightBuildDefinition );
         return processArguments;
      }

      private static string GetWorkingDirectoryOfCurrentWorkspace( TfsTeamProjectCollection tfsTeamProjectCollection, VersionControlServer versionControlServer )
      {
         var currentWorkspaceInfo = Workstation.Current.GetLocalWorkspaceInfo( versionControlServer, Workstation.Current.Name,
                                                                               string.Format( @"{0}\{1}", Environment.UserDomainName, Environment.UserName ) );
         var currentWorkspace = currentWorkspaceInfo.GetWorkspace( tfsTeamProjectCollection );

         string workingDirectory = currentWorkspace.TryGetLocalItemForServerItem( @"$\" );
         return workingDirectory;
      }

      private string GetBuildDefinitionForComparison()
      {
         string buildDefinitionForComparison = string.Empty;
         var uiShell = GetService( typeof( SVsUIShell ) ) as IVsUIShell;
         if ( uiShell != null )
         {
            var dte2 = GetDTE2();

            if ( dte2.SelectedItems.Count >= 1 )
            {
               buildDefinitionForComparison = dte2.SelectedItems.Item( 1 ).Name;
            }
         }
         return buildDefinitionForComparison;
      }

      /// <summary>
      /// This function is the callback used to execute a command when the a menu item is clicked.
      /// See the Initialize method to see how the menu item is associated to this function using
      /// the OleMenuCommandService service and the MenuCommand class.
      /// </summary>
      private void SelectLeftBuildDefinitionForComparison( object sender, EventArgs e )
      {
         LeftBuildDefinitionForComparison = GetBuildDefinitionForComparison();

         SetTextOfRightBuildDefinitionMenuCommand();
      }

      private void SetTextOfRightBuildDefinitionMenuCommand()
      {
         if ( !string.IsNullOrWhiteSpace( LeftBuildDefinitionForComparison ) )
         {
            var oleMenuCommandService = GetService( typeof( IMenuCommandService ) ) as OleMenuCommandService;
            if ( oleMenuCommandService != null )
            {
               var rightBuildDefinitionCommandId = new CommandID( GuidList.guidBuildComparisionExtensionCmdSet, (int) PkgCmdIdList.cmdidRightBuildDefinition );
               var rightBuildDefinitionMenuCommand = oleMenuCommandService.FindCommand( rightBuildDefinitionCommandId ) as OleMenuCommand;

               if ( rightBuildDefinitionMenuCommand != null )
               {
                  rightBuildDefinitionMenuCommand.Enabled = true;
                  rightBuildDefinitionMenuCommand.Text = "Compare to " + LeftBuildDefinitionForComparison;
               }
            }
         }
      }

      private DTE2 GetDTE2()
      {
         var dte = GetService( typeof( DTE ) ) as DTE;
         var dte2 = dte as DTE2;

         if ( dte2 == null )
         {
            return null;
         }

         return dte2;
      }
   }
}
